﻿/*
Author : Onur "Xtro" ER
Create date : 31.05.2009
Last update : 04.01.2012
*/

using System;
using System.Drawing;
using System.Windows.Forms;

using Xtro.MDX;
using Xtro.MDX.DXGI;
using Xtro.MDX.Direct3D10;
using Xtro.MDX.Direct3DX10;
using Color = Xtro.MDX.Direct3DX10.Color;
using Functions = Xtro.MDX.Direct3DX10.Functions;
using Error = Xtro.MDX.DXGI.Error;
using Resource = Xtro.MDX.Direct3D10.Resource;

using Xtro.Gerecler;

namespace Xtro.DirectX.Direct3D
{
    public sealed class TScreen
    {
        public struct SClearOptions
        {
            // Enable
            public bool Color;
            public bool Depth;
            public bool Stencil;
            // Value
            public Float4 ColorValue;
            public float DepthValue;
            public byte StencilValue;
        }

        public struct SInfoOptions
        {
            // General
            public bool Visible;

            /// <summary>
            /// if true, the info text is drawn in a viewport and a scissor rectange which are set by the <see cref="TScreen"/> object and this leads to a performance loss. Default is true.
            /// </summary>
            public bool UseOwnViewportAndScissorRectangle;
            public bool SaveSpriteState;
            public bool SaveDepthStencilState;
            public int Left;
            public int Top;
            public string Extra;
            // Enable
            public bool ExtraVisible;
            public bool AdaptorVisible;
            public bool ContextVisible;
            public bool ScreenVisible;
            public bool ScreenSizeVisible;
            public bool TargetControlSizeVisible;
            public bool AntialiasingVisible;
            public bool FpsVisible;
            public float FpsDanger;
            public bool VideoMemoryVisible;
            // Color
            public Color ExtraColor;
            public Color AdaptorColor;
            public Color ContextColor;
            public Color ScreenColor;
            public Color ScreenSizeColor;
            public Color TargetControlSizeColor;
            public Color AntialiasingColor;
            public Color FpsColor;
            public Color FpsDangerColor;
            public Color VideoMemoryColor;
        }

        public delegate void OEvent(TScreen Sender);
        public delegate void OEnteringFullscreen(TScreen Sender, bool WasInFullscreenAlready, uint MonitorNo, uint DisplayModeNo, uint RefreshRateNo, ModeDescription ModeDescription);
        public delegate void OEnteredFullscreen(TScreen Sender, bool WasInFullscreenAlready);
        public delegate void OResizing(TScreen Sender, Size NewSize);

        public event OResizing Resizing;
        public event OEvent Resized;
        public event OEvent Presenting;
        public event OEvent Presented;
        public event OEnteringFullscreen EnteringFullscreen;
        public event OEnteredFullscreen EnteredFullscreen;
        public event OEvent ExitFullscreen;

        public SClearOptions ClearOptions;
        public SInfoOptions InfoOptions;

        public TTexture RenderTarget { get; private set; }
        public TTexture DepthStencilTarget { get; private set; }

        public bool Idle { get; private set; }
        public TContext Context { get; private set; }

        public string Name;
        public readonly Control TargetControl;
        SwapChain SwapChain;
        Texture2D DepthStencil;

        public bool VerticalSync;

        public float Fps { get; private set; }
        float LastTickCount;
        int FrameCount;

        public void ScreenShot(string FileName, ImageFileFormat Format)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            if (string.IsNullOrEmpty(FileName)) throw new EInvalidParameter("FileName", typeof(string), string.Format(Gerecler.ResourcesReason.Empty), Name);

            Unknown Surface;
            var Result = SwapChain.GetBuffer(0, typeof(Texture2D), out Surface);
            try
            {
                if (Result != 0) throw new EDirectX("SwapChain.GetBuffer", Result, Name);
                var Texture = (Resource)Surface;

                Result = Functions.SaveTextureToFile(Texture, Format, FileName);
                if (Result != 0) throw new EDirectX("SaveTextureToFile", Result, Name);
            }
            finally
            {
                if (Surface != null) Surface.Release();
            }
        }

        bool FAutoMinimize;
        public bool AutoMinimize
        {
            get { return FAutoMinimize; }
            set
            {
                if (!(TargetControl is Form)) throw new EInvalidParameter("AutoMinimize", value.GetType(), string.Format(ResourcesReason.CantActivateOnNonFormControl, value.ToString()), Name);

                FAutoMinimize = value;
            }
        }

        FormWindowState OldWindowState;
        Size OldClientSize;
        Point OldLocation;
        bool FManageWindowState;
        public bool ManageWindowState
        {
            get { return FManageWindowState; }
            set
            {
                if (!(TargetControl is Form)) throw new EInvalidParameter("ManageWindowState", value.GetType(), string.Format(ResourcesReason.CantActivateOnNonFormControl, value.ToString()), Name);

                FManageWindowState = value;
            }
        }

        /// <param name="Name"></param>
        /// <param name="TargetControl"></param>
        /// <param name="Size">Use Size.Empty for auto-sizing</param>
        /// <param name="Context"></param>
        /// <param name="SamplingNo"></param>
        public TScreen(string Name, TContext Context, Control TargetControl, Size Size, uint SamplingNo)
        {
            if (Context == null) throw new EInvalidParameter("Context", typeof(TContext), Gerecler.ResourcesReason.NullReference, Name);
            if (TargetControl == null) throw new EInvalidParameter("TargetControl", typeof(Control), Gerecler.ResourcesReason.NullReference, Name);
            if (Size.Width < 0 || Size.Height < 0) throw new EInvalidParameter("Size", Size.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, Size.ToString()), Name);
            if (SamplingNo < 0 || SamplingNo >= Manager.FAdapters[Context.FAdapterNo].FSamplings.Count) throw new EInvalidParameter("No", SamplingNo.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, SamplingNo), Name);

            this.Name = Name;
            this.Context = Context;
            this.TargetControl = TargetControl;
            this.SamplingNo = SamplingNo;
            AutoSizing = Size.IsEmpty;

            RenderTarget = new TTexture(Name + ".RenderTarget", Context);
            DepthStencilTarget = new TTexture(Name + ".DepthStencil", Context);
            RenderTarget.DepthStencilPair = DepthStencilTarget;

            this.Size = AutoSizing ? new Size(TargetControl.ClientSize.Width > 0 ? TargetControl.ClientSize.Width : 1, TargetControl.ClientSize.Height > 0 ? TargetControl.ClientSize.Height : 1) : Size;

            ClearOptions.Color = true;
            ClearOptions.Depth = true;
            ClearOptions.Stencil = true;
            ClearOptions.ColorValue = new Float4(new[] { TargetControl.BackColor.R / 255.0f, TargetControl.BackColor.G / 255.0f, TargetControl.BackColor.B / 255.0f, TargetControl.BackColor.A / 255.0f });
            ClearOptions.DepthValue = 1;
            ClearOptions.StencilValue = 0;

            InfoOptions.Visible = false;
            InfoOptions.UseOwnViewportAndScissorRectangle = true;
            InfoOptions.SaveSpriteState = false;
            InfoOptions.SaveDepthStencilState = true;
            InfoOptions.Left = 10;
            InfoOptions.Top = 10;
            InfoOptions.Extra = ResourcesCommon.DeviceInfo + " :";
            InfoOptions.ExtraVisible = true;
            InfoOptions.AdaptorVisible = true;
            InfoOptions.ContextVisible = true;
            InfoOptions.ScreenVisible = true;
            InfoOptions.ScreenSizeVisible = true;
            InfoOptions.TargetControlSizeVisible = true;
            InfoOptions.AntialiasingVisible = true;
            InfoOptions.FpsVisible = true;
            InfoOptions.FpsDanger = 25;
            InfoOptions.VideoMemoryVisible = true;

            InfoOptions.ExtraColor = new Color(0, 1, 1, 1);
            InfoOptions.AdaptorColor = new Color(1, 1, 1, 1);
            InfoOptions.ContextColor = new Color(1, 1, 1, 1);
            InfoOptions.ScreenColor = new Color(1, 1, 1, 1);
            InfoOptions.ScreenSizeColor = new Color(1, 1, 1, 1);
            InfoOptions.TargetControlSizeColor = new Color(1, 1, 1, 1);
            InfoOptions.AntialiasingColor = new Color(1, 1, 1, 1);
            InfoOptions.FpsColor = new Color(0, 1, 0, 1);
            InfoOptions.FpsDangerColor = new Color(1, 0, 0, 1);
            InfoOptions.VideoMemoryColor = new Color(1, 1, 0, 1);

            Context.Screens.Add(this);

            TargetControl.Resize += TargetControl_Resize;
            if (TargetControl is Form)
            {
                FAutoMinimize = true;
                FManageWindowState = true;

                ((Form)TargetControl).Deactivate += TargetControl_Deactivate;
            }

            if (Context.Device != null) Start();
        }

        public void Delete()
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device != null) Stop();

            RenderTarget.Delete();
            DepthStencilTarget.Delete();

            TargetControl.Resize -= TargetControl_Resize;
            if (TargetControl is Form) ((Form)TargetControl).Deactivate -= TargetControl_Deactivate;

            Context.Screens.Remove(this);
            Context = null;
        }

        public bool AutoSizing { get; private set; }

        public void SetAutoSizing(bool Value)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (AutoSizing == Value) return;

            if (Value)
            {
                if (Context.Device != null) ResizeBuffers(TargetControl.ClientSize);
                else Size = new Size(TargetControl.ClientSize.Width > 0 ? TargetControl.ClientSize.Width : 1, TargetControl.ClientSize.Height > 0 ? TargetControl.ClientSize.Height : 1);
            }

            AutoSizing = Value;
        }

        public Size Size { get; private set; }

        public void SetSize(Size Value)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Value.Width < 1 || Value.Width > 8192 || Value.Height < 1 || Value.Height > 8192) throw new EInvalidParameter("Value", Value.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, Value.ToString()), Name);

            if (Size == Value) return;

            if (Context.Device != null) ResizeBuffers(Value);

            AutoSizing = false;

            Size = Value;
        }

        void ResizeBuffers(Size Size)
        {
            if (Size.Width < 1) Size.Width = 1;
            if (Size.Height < 1) Size.Height = 1;

            if (Size == this.Size) return;

            if (Resizing != null) Resizing(this, Size);

            RenderTarget.StopInternal();

            Exception Exception = null;

            SwapChainDescription Description;
            var Result = SwapChain.GetDescription(out Description);
            if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

            Result = SwapChain.ResizeBuffers(1, (uint)Size.Width, (uint)Size.Height, Description.BufferDescription.Format, Description.Flags);
            if (Result == (int)Windows.EError.OutOfMemory) Exception = new ENotAvailable(ResourcesCommon.RequiredResourceMemory, Name);
            else if (Result != 0) Exception = new EDirectX("SwapChain.ResizeBuffers", Result, Name);

            if (Exception != null)
            {
                try
                {
                    Size = this.Size;

                    Result = SwapChain.ResizeBuffers(1, (uint)Size.Width, (uint)Size.Height, Description.BufferDescription.Format, Description.Flags);
                    if (Result != 0) throw new EDirectX("CreateEffectFromFile", Result, Name, Exception);
                }
                catch { throw Exception; }
            }

            RenderTarget.StartFromSwapChainTextureOrDepthStencilTexture(GetSwapChainTexture(), false);

            Texture2D_Description DepthStencilDescription;
            DepthStencil.GetDescription(out DepthStencilDescription);
            DepthStencilDescription.Width = (uint)Size.Width;
            DepthStencilDescription.Height = (uint)Size.Height;

            CreateDepthStencil(ref DepthStencilDescription);

            if (Exception != null) throw Exception;

            this.Size = Size;

            if (Resized != null) Resized(this);
        }

        internal void Start()
        {
            DeactivatedInFullscreen = false;
            LastFullscreenMode = null;

            // Starting in windowed mode

            var ModeDescription = new ModeDescription
            {
                Format = Format.R8G8B8A8_UNorm,
                Width = (uint)Size.Width,
                Height = (uint)Size.Height
            };

            var SwapChainDescription = new SwapChainDescription
            {
                SampleDescription = Manager.FAdapters[Context.FAdapterNo].Samplings[(int)SamplingNo],
                BufferDescription = ModeDescription,
                Windowed = true,
                BufferCount = 1,
                OutputWindow = TargetControl.Handle,
                SwapEffect = SwapEffect.Discard,
                BufferUsage = UsageFlag.RenderTargetOutput | UsageFlag.ShaderInput,
                Flags = SwapChainFlag.AllowModeSwitch
            };

            CreateSwapChain(ref SwapChainDescription);

            var DepthStencilDescription = new Texture2D_Description
            {
                Width = ModeDescription.Width,
                Height = ModeDescription.Height,
                MipLevels = 1,
                ArraySize = 1,
                Format = Format.D24_UNorm_S8_UInt,
                SampleDescription = SwapChainDescription.SampleDescription,
                Usage = Usage.Default,
                BindFlags = BindFlag.DepthStencil,
                CpuAccessFlags = 0,
                MiscellaneousFlags = 0
            };

            CreateDepthStencil(ref DepthStencilDescription);
        }

        public bool GetIsFullscreen()
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            SwapChainDescription Description;
            var Result = SwapChain.GetDescription(out Description);
            if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

            return !Description.Windowed;
        }

        public bool GetFullscreenMode(out uint MonitorNo, out uint DisplayModeNo, out uint RefreshRateNo)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            MonitorNo = 0;
            DisplayModeNo = 0;
            RefreshRateNo = 0;

            bool Result;
            Output Output = null;
            try
            {
                var Result2 = SwapChain.GetFullscreenState(out Result, out Output);
                if (Result2 != 0) throw new EDirectX("SwapChain.GetFullscreenState", Result2, Name);

                if (Result)
                {
                    OutputDescription Description;
                    Result2 = Output.GetDescription(out Description);
                    if (Result2 != 0) throw new EDirectX("Output.GetDescription", Result2, Name);

                    var OutputMonitorNo = GetMonitorNo(Output);
                    if (OutputMonitorNo < 0) throw new EGerecler(Name, GetType(), Gerecler.ResourcesReason.UnexpectedInternalError, Name);
                    MonitorNo = (uint)OutputMonitorNo;

                    for (var A = 0; A < Manager.FAdapters[Context.FAdapterNo].FMonitors[(int)MonitorNo].FDisplayModes.Count; A++)
                    {
                        var DisplayMode = Manager.FAdapters[Context.FAdapterNo].FMonitors[(int)MonitorNo].FDisplayModes[A];

                        if (Description.DesktopCoordinates.Right - Description.DesktopCoordinates.Left == DisplayMode.Width && Description.DesktopCoordinates.Bottom - Description.DesktopCoordinates.Top == DisplayMode.Height)
                        {
                            DisplayModeNo = (uint)A;

                            for (var B = 0; B < DisplayMode.FRefreshRates.Count; B++)
                            {
                                if (FullscreenRefreshRate.Numerator == DisplayMode.FRefreshRates[B].Numerator && FullscreenRefreshRate.Denominator == DisplayMode.FRefreshRates[B].Denominator)
                                {
                                    RefreshRateNo = (uint)B;
                                    break;
                                }
                            }

                            break;
                        }
                    }
                }
            }
            finally
            {
                if (Output != null) Output.Release();
            }

            return Result;
        }

        public int GetMonitorNo()
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            Output Output;
            var Result = SwapChain.GetContainingOutput(out Output);
            if (Result != 0) throw new EDirectX("SwapChain.GetContainingOutput", Result, Name);

            return GetMonitorNo(Output);
        }

        int GetMonitorNo(Output Output)
        {
            for (var A = 0; A < Manager.FAdapters[Context.FAdapterNo].FMonitors.Count; A++)
            {
                var Monitor = Manager.FAdapters[Context.FAdapterNo].FMonitors[A];

                OutputDescription Description;
                var Result = Output.GetDescription(out Description);
                if (Result != 0) continue;

                if (Description.Monitor == Monitor.Description.Monitor) return A;
            }

            return -1;
        }

        public bool SetWindowedMode()
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            SwapChainDescription Description;
            var Result = SwapChain.GetDescription(out Description);
            if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

            if (Description.Windowed) throw new EInvalidCall(Name, GetType(), ResourcesReason.DisplayModeIsAlreadyWindowed, Name);

            Result = SwapChain.SetFullscreenState(false, null);
            if (Result == (int)Error.NotCurrentlyAvailable || Result == (int)Status.ModeChangeInProgress) return false;
            if (Result != 0) throw new EDirectX("SwapChain.SetFullscreenState", Result, Name);

            FullscreenRefreshRate = new Rational();

            if (FManageWindowState) RestoreWindow();

            if (ExitFullscreen != null) ExitFullscreen(this);

            LastFullscreenMode = null;
            DeactivatedInFullscreen = false;

            return true;
        }

        void RestoreWindow()
        {
            TargetControl.ClientSize = OldClientSize;
            TargetControl.Location = OldLocation;
            ((Form)TargetControl).WindowState = OldWindowState;
        }

        Rational FullscreenRefreshRate;

        public bool SetFullscreenMode(uint MonitorNo, uint DisplayModeNo, uint RefreshRateNo)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            if (MonitorNo >= Manager.FAdapters[Context.FAdapterNo].FMonitors.Count) throw new EInvalidParameter("MonitorNo", MonitorNo.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, MonitorNo), Name);
            if (DisplayModeNo >= Manager.FAdapters[Context.FAdapterNo].FMonitors[(int)MonitorNo].FDisplayModes.Count) throw new EInvalidParameter("DisplayModeNo", DisplayModeNo.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, DisplayModeNo), Name);
            if (RefreshRateNo >= Manager.FAdapters[Context.FAdapterNo].FMonitors[(int)MonitorNo].FDisplayModes[(int)RefreshRateNo].FRefreshRates.Count) throw new EInvalidParameter("RefreshRateNo", RefreshRateNo.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, RefreshRateNo), Name);

            if (!(TargetControl is Form)) throw new EInvalidCall(Name, GetType(), ResourcesReason.CantSwitchIntoFullscreenOnNonFormControl, Name);

            var Adapter = Manager.FAdapters[Context.FAdapterNo];
            var Monitor = Adapter.FMonitors[(int)MonitorNo];
            var DisplayMode = Monitor.FDisplayModes[(int)DisplayModeNo];

            SwapChainDescription Description;
            var Result = SwapChain.GetDescription(out Description);
            if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

            FullscreenRefreshRate = DisplayMode.RefreshRates[(int)RefreshRateNo];
            var ModeDescription = new ModeDescription
            {
                Width = DisplayMode.Width,
                Height = DisplayMode.Height,
                RefreshRate = FullscreenRefreshRate,
                Format = Description.BufferDescription.Format
            };

            if (FManageWindowState && Description.Windowed)
            {
                OldWindowState = ((Form)TargetControl).WindowState;
                ((Form)TargetControl).WindowState = FormWindowState.Normal;
                OldClientSize = TargetControl.ClientSize;
                OldLocation = TargetControl.Location;
            }

            if (EnteringFullscreen != null) EnteringFullscreen(this, !Description.Windowed, MonitorNo, DisplayModeNo, RefreshRateNo, ModeDescription);

            if (((Form)TargetControl).WindowState != FormWindowState.Normal) throw new EInvalidCall(Name, GetType(), ResourcesReason.CantSwitchIntoFullscreenOnNonNormalForm, Name);

            var OldTargetSize = ((Form)TargetControl).ClientSize;

            Result = SwapChain.ResizeTarget(ref ModeDescription);
            if (Result != 0) throw new EDirectX("SwapChain.ResizeTarget", Result, Name);

            if (Description.Windowed)
            {
                Result = SwapChain.SetFullscreenState(true, Adapter.FMonitors[(int)MonitorNo].Output);
                if (Result == (int)Error.NotCurrentlyAvailable || Result == (int)Status.ModeChangeInProgress)
                {
                    FullscreenRefreshRate = new Rational();
                    ModeDescription = new ModeDescription
                    {
                        Width = (uint)OldTargetSize.Width,
                        Height = (uint)OldTargetSize.Height,
                        RefreshRate = FullscreenRefreshRate,
                        Format = Description.BufferDescription.Format
                    };
                    Result = SwapChain.ResizeTarget(ref ModeDescription);
                    if (Result != 0) throw new EDirectX("SwapChain.ResizeTarget", Result, Name);

                    if (FManageWindowState) RestoreWindow();

                    return false;
                }
                if (Result != 0) throw new EDirectX("SwapChain.SetFullscreenState", Result, Name);

                DeactivatedInFullscreen = false;
            }

            if (EnteredFullscreen != null) EnteredFullscreen(this, !Description.Windowed);

            LastFullscreenMode = new SFullscreenMode
            {
                MonitorNo = MonitorNo,
                DisplayModeNo = DisplayModeNo,
                RefreshRateNo = RefreshRateNo
            };

            return true;
        }

        struct SFullscreenMode
        {
            public uint MonitorNo;
            public uint DisplayModeNo;
            public uint RefreshRateNo;
        }

        SFullscreenMode? LastFullscreenMode;

        internal void Stop()
        {
            try
            {
                if (SwapChain != null) SetWindowedMode();
            }
            catch (EInvalidCall E)
            {
                if (E.Reason != ResourcesReason.DisplayModeIsAlreadyWindowed) throw;
            }

            DeleteDepthStencil();
            DeleteSwapChain();
        }

        void DeleteDepthStencil()
        {
            if (DepthStencilTarget.Resource != null) DepthStencilTarget.StopInternal();

            DepthStencil = null;
        }

        void DeleteSwapChain()
        {
            if (RenderTarget.Resource != null) RenderTarget.StopInternal();

            if (SwapChain == null) return;

            SwapChain.Release();
            SwapChain = null;
        }

        void TargetControl_Deactivate(object Sender, EventArgs E)
        {
            if (Context.Device == null) return;

            SwapChainDescription Description;
            var Result = SwapChain.GetDescription(out Description);
            if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

            if (!Description.Windowed) DeactivatedInFullscreen = true;
        }
        bool DeactivatedInFullscreen;

        void TargetControl_Resize(object Sender, EventArgs E)
        {
            if (!AutoSizing) return;

            var Control = (Control)Sender;

            if (Context.Device == null) Size = new Size(Control.ClientSize.Width > 0 ? Control.ClientSize.Width : 1, Control.ClientSize.Height > 0 ? Control.ClientSize.Height : 1);
            else ResizeBuffers(((Control)Sender).ClientSize);
        }

        public uint SamplingNo { get; private set; }

        public bool SetSampling(uint No)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (No >= Manager.FAdapters[Context.FAdapterNo].FSamplings.Count) throw new EInvalidParameter("Value", No.GetType(), string.Format(Gerecler.ResourcesReason.OutOfBounds, No), Name);

            if (SamplingNo == No) return true;

            if (Context.Device != null)
            {
                uint MonitorNo, DisplayNo, RefreshRateNo;

                var OldFullscreen = GetFullscreenMode(out MonitorNo, out DisplayNo, out RefreshRateNo);
                if (OldFullscreen && !SetWindowedMode()) return false;

                SwapChainDescription SwapChainDescription;
                var Result = SwapChain.GetDescription(out SwapChainDescription);
                if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);
                SwapChainDescription.SampleDescription = Manager.FAdapters[Context.FAdapterNo].FSamplings[(int)No];

                Exception Exception = null;
                try { CreateSwapChain(ref SwapChainDescription); }
                catch (Exception E) { Exception = E; }

                if (Exception != null)
                {
                    try
                    {
                        SwapChainDescription.SampleDescription = Manager.FAdapters[Context.FAdapterNo].FSamplings[(int)SamplingNo];

                        CreateSwapChain(ref SwapChainDescription);
                    }
                    catch { throw Exception; }
                }

                Texture2D_Description DepthStencilDescription;
                DepthStencil.GetDescription(out DepthStencilDescription);
                DepthStencilDescription.SampleDescription = SwapChainDescription.SampleDescription;

                CreateDepthStencil(ref DepthStencilDescription);

                if (OldFullscreen) SetFullscreenMode(MonitorNo, DisplayNo, RefreshRateNo);

                if (Exception != null) throw Exception;
            }

            SamplingNo = No;

            return true;
        }

        void CreateSwapChain(ref SwapChainDescription SwapChainDescription)
        {
            DeleteSwapChain();

            var Result = Manager.Factory.CreateSwapChain(Context.Device, ref SwapChainDescription, out SwapChain);
            if (Result == (int)Windows.EError.OutOfMemory) throw new ENotAvailable(ResourcesCommon.RequiredResourceMemory, Name);
            if (Result != 0) throw new EDirectX("Factory.CreateSwapChain", Result, Name);

            // Stops Alt+enter from causing fullscreen skrewiness. Alt-enter always returns to window mode in .Net even in very simple triange applications of Xtro.MDX and SlimDX.
            Result = Manager.Factory.MakeWindowAssociation(TargetControl.Handle, MakeWindowAssociationFlag.NoWindowChanges);
            if (Result != 0) throw new EDirectX("Factory.MakeWindowAssociation", Result, Name);

            RenderTarget.StartFromSwapChainTextureOrDepthStencilTexture(GetSwapChainTexture(), false);
        }

        Texture2D GetSwapChainTexture()
        {
            Unknown Surface;
            var Result = SwapChain.GetBuffer(0, typeof(Texture2D), out Surface);
            if (Result != 0) throw new EDirectX("SwapChain.GetBuffer", Result, Name);

            return (Texture2D)Surface;
        }

        void CreateDepthStencil(ref Texture2D_Description Description)
        {
            DeleteDepthStencil();

            var Result = Context.Device.CreateTexture2D(ref Description, null, out DepthStencil);
            if (Result == (int)Windows.EError.OutOfMemory) throw new ENotAvailable(ResourcesCommon.RequiredResourceMemory, Name);
            if (Result != 0) throw new EDirectX("Device.CreateTexture2D", Result, Name);

            DepthStencilTarget.StartFromSwapChainTextureOrDepthStencilTexture(DepthStencil, false);
        }

        internal bool TestReady()
        {
            if (DeactivatedInFullscreen)
            {
                SwapChainDescription Description;
                var Result = SwapChain.GetDescription(out Description);
                if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

                if (Description.Windowed)
                {
                    try
                    {
                        if (FManageWindowState) RestoreWindow();

                        if (ExitFullscreen != null) ExitFullscreen(this);
                    }
                    finally { DeactivatedInFullscreen = false; }

                    if (FAutoMinimize) ((Form)TargetControl).WindowState = FormWindowState.Minimized;
                }
            }

            if (FAutoMinimize && ((Form)TargetControl).WindowState != FormWindowState.Minimized && LastFullscreenMode.HasValue)
            {
                SwapChainDescription Description;
                var Result = SwapChain.GetDescription(out Description);
                if (Result != 0) throw new EDirectX("SwapChain.GetDescription", Result, Name);

                if (Description.Windowed)
                {
                    if (((Form)TargetControl).WindowState == FormWindowState.Maximized) ((Form)TargetControl).WindowState = FormWindowState.Normal;

                    SetFullscreenMode(LastFullscreenMode.Value.MonitorNo, LastFullscreenMode.Value.DisplayModeNo, LastFullscreenMode.Value.RefreshRateNo);
                }
            }

            // Idle state management by the Present result
            if (Idle)
            {
                var Result = SwapChain.Present(0, PresentFlag.Test);
                if (Result == (int)Status.Occluded) return false;
                if (Result != 0) throw new EDirectX("SwapChain.Present", Result, Name);

                Idle = false;
            }

            return true;
        }

        internal void Clear()
        {
            if (ClearOptions.Color) RenderTarget.Clear(ref ClearOptions.ColorValue);

            ClearFlag Flags = 0;
            if (ClearOptions.Depth) Flags |= ClearFlag.Depth;
            if (ClearOptions.Stencil) Flags |= ClearFlag.Stencil;

            if (Flags > 0) DepthStencilTarget.Clear(Flags, ClearOptions.DepthValue, ClearOptions.StencilValue);
        }

        Viewport InfoViewport = new Viewport
        {
            Width = 1,
            Height = 1,
            MinimumDepth = 0,
            MaximumDepth = 1,
            TopLeftX = 0,
            TopLeftY = 0
        };

        Rectangle InfoScissor = new Rectangle(0, 0, 1, 1);

        void DrawInfo()
        {
            RenderTarget.SetOutputTarget();
            
            Viewport[] OldViewports = null;
            Rectangle[] OldScissors = null;
            if (InfoOptions.UseOwnViewportAndScissorRectangle)
            {
                var Count = 0u;
                Context.Device.RS_GetViewports(ref Count, null);
                OldViewports = new Viewport[Count];
                Context.Device.RS_GetViewports(ref Count, OldViewports);

                InfoViewport.Width = (uint)Size.Width;
                InfoViewport.Height = (uint)Size.Height;
                Context.Device.RS_SetViewports(1, new[] { InfoViewport });

                RasterizerState RasterizerState;
                Context.Device.RS_GetState(out RasterizerState);
                if (RasterizerState != null)
                {
                    RasterizerDescription Description;
                    RasterizerState.GetDescription(out Description);
                    if (Description.ScissorEnable)
                    {
                        Context.Device.RS_GetScissorRectangles(ref Count, null);
                        OldScissors = new Rectangle[Count];
                        Context.Device.RS_GetScissorRectangles(ref Count, OldScissors);

                        InfoScissor.Width = Size.Width - InfoScissor.Left;
                        InfoScissor.Height = Size.Height - InfoScissor.Top;
                        Context.Device.RS_SetScissorRectangles(1, new[] { InfoScissor });
                    }
                }
            }

            var Adapter = Manager.FAdapters[Context.FAdapterNo];

            Context.BeginDrawText(InfoOptions.SaveDepthStencilState, InfoOptions.SaveSpriteState);
            try
            {
                var TextBox = new Rectangle(InfoOptions.Left, InfoOptions.Top, Size.Width, Size.Height);
                if (InfoOptions.ExtraVisible) TextBox.Y += Context.DrawText(ref TextBox, InfoOptions.Extra, ref InfoOptions.ExtraColor, FontDrawFlag.NoClip);
                if (InfoOptions.AdaptorVisible) TextBox.Y += Context.DrawText(ref TextBox, Adapter.Description.Description, ref InfoOptions.AdaptorColor, FontDrawFlag.NoClip);
                if (InfoOptions.ContextVisible) TextBox.Y += Context.DrawText(ref TextBox, Context.Name, ref InfoOptions.ContextColor, FontDrawFlag.NoClip);
                if (InfoOptions.ScreenVisible) TextBox.Y += Context.DrawText(ref TextBox, Name, ref InfoOptions.ScreenColor, FontDrawFlag.NoClip);
                if (InfoOptions.ScreenSizeVisible) TextBox.Y += Context.DrawText(ref TextBox, ResourcesCommon.ScreenSize + " : " + Size.Width + " x " + Size.Height, ref InfoOptions.ScreenSizeColor, FontDrawFlag.NoClip);
                if (InfoOptions.TargetControlSizeVisible) TextBox.Y += Context.DrawText(ref TextBox, ResourcesCommon.TargetControlSize + " : " + TargetControl.ClientSize.Width + " x " + TargetControl.ClientSize.Height, ref InfoOptions.TargetControlSizeColor, FontDrawFlag.NoClip);
                if (InfoOptions.AntialiasingVisible) TextBox.Y += Context.DrawText(ref TextBox, ResourcesCommon.Antialiasing + " : " + Adapter.Samplings[(int)SamplingNo].Count + ":" + Adapter.Samplings[(int)SamplingNo].Quality, ref InfoOptions.AntialiasingColor, FontDrawFlag.NoClip);
                if (InfoOptions.FpsVisible)
                {
                    var Color = Fps < InfoOptions.FpsDanger ? InfoOptions.FpsDangerColor : InfoOptions.FpsColor;
                    TextBox.Y += DrawFps(TextBox.X, TextBox.Y, ResourcesCommon.FPS + " : ", ref Color, InfoOptions.SaveDepthStencilState, InfoOptions.SaveSpriteState);
                }
                if (InfoOptions.VideoMemoryVisible)
                {
                    var AdapterDescription = Manager.FAdapters[Context.FAdapterNo].Description;

                    TextBox.Y += Context.DrawText(ref TextBox, string.Format(ResourcesCommon.VideoMemory + " : {0:0,0} MB", AdapterDescription.DedicatedVideoMemory / 1048576), ref InfoOptions.VideoMemoryColor, FontDrawFlag.NoClip);
                    TextBox.Y += Context.DrawText(ref TextBox, string.Format(ResourcesCommon.SharedMemory + " : {0:0,0} MB", AdapterDescription.SharedSystemMemory / 1048576), ref InfoOptions.VideoMemoryColor, FontDrawFlag.NoClip);
                }
            }
            finally { Context.EndDrawText(); }
            
            if (InfoOptions.UseOwnViewportAndScissorRectangle)
            {
                // ReSharper disable PossibleNullReferenceException
                Context.Device.RS_SetViewports((uint)OldViewports.Length, OldViewports);
                // ReSharper restore PossibleNullReferenceException
                if (OldScissors != null) Context.Device.RS_SetScissorRectangles((uint)OldScissors.Length, OldScissors);
            }
        }

        public int DrawFps(int X, int Y, string Text, ref Color Color, bool SaveDepthStencilState = false, bool SaveSpriteState = false)
        {
            if (Context == null) throw new EInvalidCall(Name, GetType(), ResourcesReason.DeletedAndCantExecuteAnything, Name);

            if (Context.Device == null) throw new EInvalidCall(Context.Name, Context.GetType(), ResourcesReason.NotActive, Name);

            RenderTarget.SetOutputTarget();

            Text += Fps.ToString("f2");
            var TextBox = new Rectangle(X, Y, Size.Width, Size.Height);
            return Context.DrawText(ref TextBox, Text, ref Color, FontDrawFlag.NoClip, SaveDepthStencilState, SaveSpriteState);
        }

        internal void Present()
        {
            if (Presenting != null) Presenting(this);

            if (InfoOptions.Visible) DrawInfo();

            var SyncInterval = (uint)(VerticalSync ? 1 : 0);

            var Result = SwapChain.Present(SyncInterval, 0);
            if (Result == (int)Status.Occluded)
            {
                Idle = true;
                return;
            }
            if (Result != 0) throw new EDirectX("SwapChain.Present", Result, Name);

            FrameCount++;
            var CurrentTickCount = Windows.TickCount();
            var ElapsedTickCount = CurrentTickCount - LastTickCount;
            if (ElapsedTickCount >= 1000)
            {
                Fps = FrameCount * 1000 / ElapsedTickCount;
                LastTickCount = CurrentTickCount;
                FrameCount = 0;
            }

            if (Presented != null) Presented(this);
        }
    }
}